package org.yinxue.swing.unit.model;


import fun.codedesign.yinxue.util.StringUtil;
import org.yinxue.swing.unit.constant.CommonType;
import org.yinxue.swing.unit.constant.UnitConstant;
import org.yinxue.swing.unit.context.Context;
import org.yinxue.swing.unit.util.UnitUtil;
import org.yinxue.swing.unit.visitor.MockitoVisitor;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 方法体中的子方法 <br>
 *
 * @author zengjian
 * @create 2018-05-14 9:28
 * @since 1.0.0
 */
public class StatementDesc {

    public String methodLineAfter;
    public String methodLine;
    public String methodContext;
    public ClassDesc parent;

    public String methodName;
    public String methodVariable;
    public Map<String, String> params;
    public String methodClassName;
    public String returnType;
    public String returnParam;
    public String returnInitValue;

    //public Map<String, Integer> unitVariableTable = new HashMap<>();  //变量命名去重表
    public MethodDesc parentMethodDesc;


    /**
     * 构造需要进行mock的子方法<br>
     * <pre>
     *
     * 边界:
     * 1. 匹配到方法级联情况: memberAccountRouteService.findAccountRepository(AccountType.initFrom(addStruct.getAccountType())).queryAccountByAcctType(custNum, acctType,ecoType);
     * 2.
     *
     *
     * <pre/>
     *
     * @param methodLine    正则匹配的方法行，一般为带^ ..[Service|Dao|..] ;$的方法
     * @param parentContext 所在方法的文本内容
     * @param parentMethodDesc  所属于的父类方法
     */
    public StatementDesc(String methodLine, String parentContext, MethodDesc parentMethodDesc) {
        this.methodLine = methodLine.trim().replaceAll(";", "");
        // 校验多余的括号
        this.methodLine = UnitUtil.trimeUnneededBracket(this.methodLine);
        /*
         * TODO 匹配到方法级联调用需要再进行一次拆分，无法判断是否为级联调用，直接进行切分处理
         * 1. 切分级联前半部分
         * 2. 记录级联的后半部分，视情况处理
         */
        this.methodLine = subHalfMethodLine(this, this.methodLine);
        this.methodContext = parentContext;
        this.parentMethodDesc = parentMethodDesc;
        this.parent = this.parentMethodDesc.parent;
        this.initChildMethodLines();
    }

    /**
     * 转换为char数组，然后逐个探测(),直到探测的count计数为0返回，记录下下标截取<br>
     * <pre>
     *     边界：
     *     1. xxxxx.yyyy()
     *     2. xxxxx.hello(xxxx,xxx)
     *     3. xxxxx.hello((<>),())
     * </pre>
     *
     * @param methodLine
     * @return
     */
    private String subHalfMethodLine(StatementDesc child, String methodLine) {
        char[] charArray = methodLine.toCharArray();
        int count = 0;
        int index = charArray.length - 1;
        boolean flag = false; //为了更安全的解析
        for (int i = 0, length = charArray.length; i < length; i++) {
            if (charArray[i] == '(') {
                count++;
                flag = true;
            } else if (charArray[i] == ')') {
                count--;
            }
            if (count == 0 && flag) {
                index = i;
                break;
            }
        }
        // 例如  memberAccountRouteService.findBatchRepository(entry.getKey())
        String halfMethodLine = methodLine.substring(0, index + 1);
        if (!halfMethodLine.equals(methodLine)) {
            // 例如:  .insertBatch(req, batchEntity)
            child.methodLineAfter = methodLine.substring(index + 1, charArray.length);
        }
        return halfMethodLine;
    }

    private void initChildMethodLines() {
        this.methodVariable = methodLine.split("\\.")[0];
        this.methodName = resolveChildMethodName(methodLine);
        this.params = findEntryParam(methodLine);
        this.methodClassName = findClassName();
        // 创建child key，然后根据这个在容器中去寻找
    }

    private String resolveChildMethodName(String line) {
        return line.substring(line.indexOf(".") + 1, line.indexOf("("));
    }

    private Map<String, String> findEntryParam(String methodLine) {
        Map<String, String> paramMap = new LinkedHashMap<>();
        String[] params = UnitUtil.resolveEntryParamArray(methodLine);
        int count = 0;
        for (int i = 0; i < params.length; i++) {
            String paramType = findEntryParamType(this, params[i]);
            if (paramMap.containsKey(params[i])) {
                paramMap.put(params[i] + (++count), paramType);
                continue;
            }
            paramMap.put(params[i], paramType);
        }
        return paramMap;
    }

    private String findClassName() {
        // 如果依然为空，在父类中查找，内部类的情况，可以用for循环
        for (ClassDesc parent = this.parent; parent != null; parent = parent.parent) {
            FieldDesc fieldDesc = parent.fieldMap.get(this.methodVariable);
            if (fieldDesc != null) {
                return fieldDesc.type;
            }
        }

        // 如果为空，在方法上下文中查找
        String type = findEntryParamType(this, this.methodVariable);
        if (!UnitConstant.PLACE_HOLDER.equals(type)) {
            return type;
        }
        // 以上依然找不到，那再次判断是否为大写字母开头，如果是，说明是静态方法，methodClassName == methodVariable
        if (this.methodVariable.matches("^[A-Z]{1}[^=]*")) {
            this.methodClassName = this.methodVariable;
        }
        return this.methodClassName == null ? "" : this.methodClassName;
    }

    /**
     * 取方法的入参 <br>
     * 这里如果最后还是为"" 则返回 {@link String} <br>
     *
     * @param statementDesc
     * @param param 入参行
     * @return
     */
    protected String findEntryParamType(StatementDesc statementDesc, String param) {
        // 如果得到的参数是null，返回未知
        if (param.equals("null")) {
            return UnitConstant.PLACE_HOLDER;
        }
        // 如果是主方法的入参，那么就直接提取主方法的入参类型
        if (statementDesc.parentMethodDesc.params.containsKey(param)) {
            return statementDesc.parentMethodDesc.params.get(param);
        }

        String paramType = findParamType(param, statementDesc.parentMethodDesc.methodLines);

        if ("".equals(paramType)) {
            if (param.contains("(")) {
                // 先占位然后交给容器来查找
                paramType = UnitConstant.PLACE_HOLDER;
            } else {
                // 在方法体中找不到，尝试去类中的FieldType中找一下，如果这是一个内部类的话
                for (ClassDesc parent = statementDesc.parent; parent != null; parent = parent.parent) {
                    // 先从fieldMap中找，如果找到了，直接返回，没找到，再全文扫描
                    FieldDesc fieldDesc = this.parent.fieldMap.get(param);
                    if (fieldDesc != null) {
                        paramType = fieldDesc.type;
                        return paramType;
                    }

                }
                // 从继承类中查找 HACK 需要优化
                if ("".equals(paramType)) {
                    for (ClassDesc parent = statementDesc.parent; parent != null; parent = parent.parent) {
                        paramType = findParamType(param, parent.lines);
                        if ("".equals(paramType)) {
                            if (parent.superClassRef.size() > 0) {
                                for (Map.Entry<String, ClassDesc> superDefinition : parent.superClassRef.entrySet()) {
                                    // 未进行容器注入的，对象为null
                                    if (superDefinition.getValue() == null) {
                                        continue;
                                    }
                                    paramType = findParamType(param, superDefinition.getValue().lines);
                                    if (!"".equals(paramType)) {
                                        return paramType;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        if (paramType.contains("<")) {
            paramType = paramType.replaceAll("<[\\s\\S]*>", "").trim();
        }
        if ("".equals(paramType)) {
            paramType = UnitConstant.PLACE_HOLDER;
        }
        return paramType;
    }

    /**
     * @param param 变量
     * @param methodLines 方法体数组
     * @return
     */
    private String findParamType(String param, String[] methodLines) {
        if (methodLines == null) {
            return "";
        }

        String paramType = "";
        // TODO 修改为从匹配行往前匹配，防止在不同作用域中取了名称相同的变量，比如
        /*
         * //变化量大于0即充值，变化量小于0即扣减
         if (changeAmt.compareTo(BigDecimal.ZERO) > 0) {
         RechargeSupplierAccountReq req = new RechargeSupplierAccountReq();
         req.setCustNum(custNum);
         req.setEventNum(ConstantsCAMP.SYSTEM_NAME + DateUtil.getCurrentTimeForNasecond().substring(0, 14));
         req.setEventTs(currentDate);
         req.setRechargeAmt(changeAmt);
         supplierAccountService.rechargeByHand(req);
         } else {
         UpdateSupplierAccountReq req = new UpdateSupplierAccountReq();
         req.setCustNum(custNum);
         req.setAcctType(AcctType.BASICPOINT.getCode());
         req.setEventNum(ConstantsCAMP.SYSTEM_NAME + DateUtil.getCurrentTimeForNasecond().substring(0, 14));
         req.setEventTs(currentDate);
         req.setChangeAmt(changeAmt.abs());
         req.setScenarioCode(ScenarioCode.DECREASEDBYHAND.getCode());
         supplierAccountService.addSubstractAccount(req);
         }
         */
        // 先找到 methodLine所在的数组下标，然后从下标往上走
        int countIndex = methodLines.length - 1;
        for (int i = 0, size = methodLines.length; i < size; i++) {
            if (methodLines[i].contains(this.methodLine)) {
                countIndex = i;
                break;
            }
        }

        out:
        for (int j = countIndex/*, size = methodLines.length*/; j >= 0; j--) {
            String line = methodLines[j];
            if (line.contains(" " + param + " ") || line.contains(" " + param + ";")) {
                // 情况1： ClassType param 格式的情况: 得到这一行，从变量往前探测，当遇见 或者是空格，或者是说到头了停止
                // 情况2: 变量被赋予其他的变量  ClassType var = param , classType[] var = param; classType<String,Object> var = param;
                String text = line.substring(0, line.indexOf(param)).trim();
                // 消除记录日志的情况，如果 = 不是最后一位，说明前面还有相应的字符，比如日志等情况，跳出
                // 情况1，消除是+号记日志的情况，比如 throw new Exception(" 1231231 " + param.toString + "" )
                if (text.endsWith("+")) {
                    break;
                }
                if (text.endsWith("=")) {
                    text = text.substring(0, text.lastIndexOf("="));
                    text = text.split(" ")[0].replaceAll("<[^=]*", "").trim();
                    paramType = text;
                } else {
                    char[] arr = text.toCharArray();
                    int length = arr.length - 1;
                    int count = 0;
                    for (int i = length; i >= 0; i--) {
                        if (arr[i] == '>') {
                            count++;
                            continue;
                        }
                        if (arr[i] == '<') {
                            count--;
                            continue;
                        }
                        if (count == 0 && (arr[i] == ' ' || arr[i] == '(' || i == 0)) {
                            paramType = new String(Arrays.copyOfRange(arr, i == 0 ? 0 : i + 1, arr.length)).trim();
                            break out;
                        }
                    }
                }
            }
        }
        return paramType;
    }

    /**
     * 这里取子方法的返回值，依次的顺序是寻找方法上下文，类上下文，容器 <br>
     *
     * @param statement 子方法
     * @param line e.g. matchAccountDao.queryDepositAmt(custNum)
     */
    public void findReturnParam(StatementDesc statement, String line) {
        // 替换括号避免正则匹配错误
        String textMatch = line.replace("(", "\\(");
        textMatch = textMatch.replace(")", "\\)");
        //  返回的可能是带类型声明的变量 ClassType returnParam = line; e.g. Entity entity = ...
        //  返回的可能是没有赋值的变量 ClassType returnParam;
        //  返回的可能是一个变量  returnParam = ...  e.g. resp = ...
        // TODO 如果没有级联方法，这样判断返回是没问题的，如果有则不这样判断
        if (statement.methodLineAfter == null) {
            Pattern pattern = Pattern.compile("[A-Za-z<, >]+ {1,3}[A-Za-z0-9_]+[\n| ]*=[\n| ]*" + textMatch);
            Matcher matcher = pattern.matcher(statement.methodContext);
            while (matcher.find()) {
                String returnTypeLine = matcher.group();
                String subLine = returnTypeLine.substring(0, returnTypeLine.indexOf("=")).trim();
                if (subLine.contains(" ")) {
                    statement.returnType = subLine.split(" +")[0];
                    statement.returnParam = subLine.split(" +")[1];
                    break;
                } else {
                    statement.returnParam = subLine.split(" +")[0];
                    statement.returnType = findReturnTypeByParam(statement, statement.returnParam);
                    break;
                }
            }
        }

        if (statement.returnType == null) {
            // 从继承类中去查找，多重继承的情况下
            String fullClassName = UnitUtil.fullClassName(statement.methodClassName, statement.parent);
            findReturnTypeByContext(statement, Context.register().getClassDesc(fullClassName), statement.methodName);
        }

        if (statement.returnType == null || statement.returnType.equals("void")) {
            // 不需要进行拼接
            return;
        }

        if (statement.returnType.contains("<")) {
            statement.returnType = statement.returnType.replaceAll("<[^=]*", "").trim();
        }
        // TODO 给一个初始值即可
        statement.returnInitValue = MockitoVisitor.getParamInitValue(statement.returnType, statement.parentMethodDesc);
        statement.returnParam = getDefaultReturnParam(statement.returnType);
    }



    private String getDefaultReturnParam(String returnType) {
        String returnVar = CommonType.getInitParamVariable(returnType);
        if (returnVar == null){
            returnVar = StringUtil.toLowerCaseFirstChar(returnType);
        }
        return returnVar;
    }

    /**
     * 递归的方法来查找方法的返回类型，那也可以用这个类似的方法来查找传入的参数类型
     *
     * @param statement
     * @param classDesc
     * @param methodName
     */
    private void findReturnTypeByContext(StatementDesc statement, ClassDesc classDesc, String methodName) {
        if (classDesc != null) {
            Map<String, MethodDesc> map = classDesc.methodMap;
            for (Map.Entry<String, MethodDesc> methodEntry : map.entrySet()) {
                // 比较方法名称和参数类型
                MethodDesc methodDesc = methodEntry.getValue();
                if (methodName.equals(methodDesc.getMethodName()) && statement.compositParamType().equals(methodDesc.compositParamType())) {
                    // 同上需要得到一个新的对象，而不是去改变这个对象
                    statement.returnType = new String(methodEntry.getValue().returnType);
                    return;
                }
            }
            // 再继续找它的父类
            Map<String, ClassDesc> superMap = classDesc.superClassRef;
            if (superMap.size() > 0) {
                for (Map.Entry<String, ClassDesc> entry : superMap.entrySet()) {
                    ClassDesc cache = entry.getValue();
                    if (cache == null) {
                        cache = Context.register().getClassDesc(UnitUtil.fullClassName(entry.getKey(), classDesc));
                    }
                    if (cache == null) {
                        return;
                    }
                    findReturnTypeByContext(statement, cache, methodName);
                }
            }
        }
    }

    private String findReturnTypeByParam(StatementDesc statmentDesc, String returnParam) {
        Pattern pattern = Pattern.compile("[A-Za-z0-9_]+ +" + returnParam + " *=[\\S\\s]*;");
        Matcher matcher = pattern.matcher(statmentDesc.methodContext);
        String type = null;
        if (matcher.find()) {
            String line = matcher.group();
            type = line.split(" +")[0];
        }
        return type;
    }

    // 获取参数组合类型串，以，隔开
    public String compositParamType(){
        Iterator<Map.Entry<String,String>> iterator = params.entrySet().iterator();
        StringBuilder builder = new StringBuilder(16);
        while (iterator.hasNext()){
            builder.append(iterator.next().getValue());
            if (iterator.hasNext()){
                builder.append(",");
            }
        }
        return builder.toString();
    }

}
